#include "stdafx.h"
#include <windows.h>
#include <wincrypt.h>
#include "VerifyCPP.h"
#include "GemsecUtilities.h"

#using <mscorlib.dll>
#using <system.dll>
#using <system.xml.dll>
#using <system.security.dll>

using namespace System;
using namespace System::Collections;
using namespace System::Configuration;
using namespace System::Xml;
using namespace System::Security::Cryptography;
using namespace System::Security::Cryptography::X509Certificates;
using namespace System::Security::Cryptography::Xml;

namespace Safe {


	/// We have to check the time validity in the chain ourselves, since CAPI will
	/// set the dwErrorStatus code to CERT_TRUST_IS_NOT_TIME_VALID if the end cert
	/// is not time valid according to the system clock.
	bool CertificateValidation::CheckChainTimeValidity(
			PCERT_SIMPLE_CHAIN pChain,
			FILETIME validationTime)
	{
		if( pChain == NULL ) { 
			return false;
		}

		// if the chain build function has already determined that the chain is time
		// valid, then return true.
		if( 0 == (pChain->TrustStatus.dwErrorStatus & CERT_TRUST_IS_NOT_TIME_VALID)) {
			return true;
		} else {
			bool allValid = true;
			/// use cElement-1 to still require the root cert to be time valid
			/// at the current time
			for( unsigned int idx=0; allValid && idx< pChain->cElement - 1; idx++ ) {
				FILETIME nb = pChain->rgpElement[idx]->pCertContext->pCertInfo->NotBefore;
				FILETIME na = pChain->rgpElement[idx]->pCertContext->pCertInfo->NotAfter;
				
				allValid &= 
					(CompareFileTime( &validationTime, &nb ) >= 0 &&
					 CompareFileTime( &validationTime, &na ) <= 0);
			}
			return allValid;
		}
	}



	Safe::PathResult CertificateValidation::ValidateCertificate(
		System::Security::Cryptography::X509Certificates::X509Certificate2 ^cert, 
		System::Collections::Generic::List<System::String^>^ base64OcspResponses, 
		System::Collections::Generic::List<System::String^>^ safePolicyOids,
		System::DateTime ^timestamp,
		long clockSkew) 
	{

		//-------------------------------------------------------
		// Get the cert context
		PCCERT_CONTEXT pCertificate = (PCCERT_CONTEXT)(void *)cert->Handle;

		//-------------------------------------------------------
		// Build a certificate chain without revocation checking
		PCCERT_CHAIN_CONTEXT     pChainContext;
		PCCERT_CONTEXT           pCertContext = NULL;
		CERT_CHAIN_PARA          chainPara;
		CERT_USAGE_MATCH		 certUsage;
		// check the certificate chain for SAFE policy
		CERT_USAGE_MATCH		 certPolicy;

		DWORD                    dwFlags=0;
		
		memset(&certUsage, 0, sizeof(CERT_USAGE_MATCH));
		memset(&certPolicy, 0, sizeof(CERT_USAGE_MATCH));
		memset(&chainPara, 0, sizeof(CERT_CHAIN_PARA));

		if( safePolicyOids != nullptr && safePolicyOids->Count > 0 ) { 
			certPolicy.dwType = USAGE_MATCH_TYPE_OR;
			certPolicy.Usage.cUsageIdentifier = safePolicyOids->Count;
			certPolicy.Usage.rgpszUsageIdentifier = new LPSTR[ certPolicy.Usage.cUsageIdentifier ];
			for( int policyIdx = 0; policyIdx < safePolicyOids->Count; policyIdx++ ) { 
				System::IntPtr ptr = System::Runtime::InteropServices::Marshal::StringToHGlobalAnsi(safePolicyOids[policyIdx]);
				certPolicy.Usage.rgpszUsageIdentifier[policyIdx] = static_cast<char*>(ptr.ToPointer());
			}
		}
		
		// Set the chain building parameters
		// (Note:  aside from .RequestedUsage and .cbSize, the
		// rest of the members of the CERT_CHAIN_PARA struct are
		// not accessible by default.  The #define macro for 
		// CERT_CHAIN_PARA_HAS_EXTRA_FIELDS in stdafx.h makes the
		// rest of these fields visible).  This allows 
		// specifying the policy information when building the 
		// chain rather than looking for it after the chain has 
		// been built.
		chainPara.cbSize = sizeof(CERT_CHAIN_PARA);
		chainPara.RequestedUsage=certUsage;
		chainPara.RequestedIssuancePolicy=certPolicy;
		chainPara.dwRevocationFreshnessTime = 0;
		chainPara.dwUrlRetrievalTimeout = 0;
		chainPara.fCheckRevocationFreshnessTime = FALSE;
		chainPara.RequestedUsage = certUsage;

		FILETIME ft;
		ft.dwHighDateTime = (int)(timestamp->ToFileTimeUtc() >> 32);
		ft.dwLowDateTime = (int)(timestamp->ToFileTimeUtc() & 0xFFFFFFFF);

		// note that the timestamp passed in does not affect root certificate time-validity
		// checking.  If the root cert is expired, the chain is invalid.
		if(CertGetCertificateChain(
			NULL,					// use the default chain engine
			pCertificate,			// pointer to the end certificate
			&ft,						// use the passed-in time
			NULL,					// search no additional stores
			&chainPara,				// contains policy processing information
			CERT_CHAIN_TIMESTAMP_TIME,	// dwFlags,
			NULL,					// currently reserved
			&pChainContext))		// return a pointer to the chain created
		{
			System::Diagnostics::Debug::WriteLine("The chain has been created. \n");
		}
		else
		{
			throw gcnew System::Exception("The chain could not be created.");
		}

		// release the allocated policy information
		if( certPolicy.Usage.cUsageIdentifier > 0 ) { 
			for( unsigned int policyIdx = 0; policyIdx < certPolicy.Usage.cUsageIdentifier; policyIdx++ ) { 
				System::Runtime::InteropServices::Marshal::FreeHGlobal( 
					(IntPtr)certPolicy.Usage.rgpszUsageIdentifier[policyIdx] );
			}
		}

		DWORD chainErr = pChainContext->TrustStatus.dwErrorStatus;
		// remove revocation errors.  we're doing our own revocation checking with 
		// the passed-in OCSP responses.  Also, remove non-standard MS chain building
		// errors that aren't really errors according to the RFC, and remove the 
		// time validity error, since this is checked manually as well. (See the
		// CheckChainTimeValidity function documentation for details)
		chainErr &= REMOVE_REVOCATION_STATUS_MASK;
		chainErr &= REMOVE_NONSTANDARD_ERRORS_MASK;
		chainErr &= REMOVE_TIMEVALID_ERROR_MASK;

		// if the chain wasn't built, throw an Exception
		if( chainErr == CERT_TRUST_IS_PARTIAL_CHAIN ) { 
			return PathResult::CertUntrusted;
		} else if( chainErr != 0 ) { 
			if( 0 != (chainErr & CERT_TRUST_IS_NOT_VALID_FOR_USAGE) ) { 
				return PathResult::CertFailedPolicyCheck;
			} else { 				
				return PathResult::CertInvalid;
			}
		}
		

		bool validChainFound = false;
		PathResult firstPathResult = PathResult::CertInvalid;
		for( unsigned int chainCounter = 0; false == validChainFound && chainCounter < pChainContext->cChain; chainCounter++ ) { 

			PathResult pValid = PathResult::CertInvalid;
			System::Diagnostics::Trace::WriteLine("Trying chain " + chainCounter + "...");

			// try to validate the simple chain
			pValid = TryChain( pChainContext->rgpChain[chainCounter], base64OcspResponses, clockSkew, ft );
			if( chainCounter == 0 ) { 
				firstPathResult = pValid;
			}
			validChainFound = (pValid == PathResult::Good);
			System::Diagnostics::Trace::WriteLine("Chain validation result: " + pValid.ToString() );
		}

		return validChainFound ? PathResult::Good : firstPathResult;
	}
	

	System::String^ CertificateValidation::GetLastErrorAsString() { 
		DWORD code = GetLastError();
		LPVOID lpMsgBuf;
		DWORD formattedChars = FormatMessage(
			FORMAT_MESSAGE_ALLOCATE_BUFFER | 
			FORMAT_MESSAGE_FROM_SYSTEM | 
			FORMAT_MESSAGE_IGNORE_INSERTS,
			NULL,
			code,
			MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
			(LPTSTR) &lpMsgBuf,
			0,
			NULL
		);
		
		if( lpMsgBuf != NULL ) { 
			System::String^ errMsg = gcnew System::String((LPTSTR)lpMsgBuf);			
			LocalFree(lpMsgBuf);
			return errMsg;
		} else {
			return "Error string failure";
		}
	}

	/**
	Tries to validate revocation a simple cert chain at the given time.  It is assumed that the chain
	passed into this function has already passed time-validity and signature checks, and all that needs
	to be done is checking OCSP revocation information.
	The following steps are performed:
		For each certificate in the chain (except the root certificate):
			1.  An OCSP response appropriate for the certificate is located in the list
			2.  The OCSP response is parsed, and the signature on the OCSP response is checked
			3.  The OCSP response is checked for time-validity
			4.  The OCSP responder certificate is signature-verified and the time-validity is checked
			5.  The certificate status is verified to be OK in the OCSP response
	*/
	PathResult CertificateValidation::TryChain( 
		PCERT_SIMPLE_CHAIN chain, 
		System::Collections::Generic::List<System::String^>^ base64OcspResponses, 
		long clockSkew,
		FILETIME validationTime ) 
	{
		if( !CertificateValidation::CheckChainTimeValidity(chain, validationTime) ) {
			return PathResult::ChainNotTimeValid;
		}
	
		PathResult rval = PathResult::Good;
		for( unsigned int certIdx = 0; rval == PathResult::Good && certIdx < chain->cElement - 1;  certIdx++ ) { 
			// 1.  Find the OCSP response for this certificate
			bool ocspFoundForCert = false;
			for( int ocspIdx = 0; ocspIdx < base64OcspResponses->Count && !ocspFoundForCert; ocspIdx++ ) {
				BYTE* bOcspResponse = NULL;
				DWORD cbOcspResponse = 0;

#ifdef USING_OCSP_CRLS_ONLY
				array<unsigned char>^ bResponse = Convert::FromBase64String(base64OcspResponses[ocspIdx]);
				BYTE* bCapiOcsp = new BYTE[ bResponse->Length ];
				DWORD cCapiOcsp = bResponse->Length;
				System::Runtime::InteropServices::Marshal::Copy( bResponse, 0, (IntPtr)bCapiOcsp, bResponse->Length );
				PCCRL_CONTEXT pCrl = CertCreateCRLContext( 
					PKCS_7_ASN_ENCODING|X509_ASN_ENCODING, 
					bCapiOcsp,
					cCapiOcsp);
				delete [] bCapiOcsp;
				bCapiOcsp = NULL;
				cCapiOcsp = 0;

				// check the crl for appropriate thisUpdate and nextUpdate times
				/*tu = pCrl->pCrlInfo->ThisUpdate;
				nu = pCrl->pCrlInfo->NextUpdate;
				
				dtThisUpdate = DateTime::FromFileTimeUtc(
					(((__int64)tu.dwHighDateTime) << 32) | (tu.dwLowDateTime & 0xFFFFFFFF));
				System::DateTime dtNextUpdate = DateTime::FromFileTimeUtc(
					(((__int64)nu.dwHighDateTime) << 32) | (nu.dwLowDateTime & 0xFFFFFFFF));
				System::DateTime dtValTime = DateTime::FromFileTimeUtc( 
					(((__int64)validationTime.dw/HighDateTime) << 32) | (validationTime.dwLowDateTime & 0xFFFFFFFF));*/
				// get the raw OCSP response
				PCERT_EXTENSION pExt = CertFindExtension( 
					szOID_PKIX_OCSP_BASIC_SIGNED_RESPONSE, 
					pCrl->pCrlInfo->cExtension, 
					pCrl->pCrlInfo->rgExtension );
				
				cbOcspResponse = pExt->Value.cbData;
				bOcspResponse = new BYTE[ cbOcspResponse ];
				memcpy( pExt->Value.pbData, bOcspResponse, sizeof(BYTE)*cbOcspResponse );

#else 
				array<unsigned char>^ bResponse = Convert::FromBase64String(base64OcspResponses[ocspIdx]);
				bOcspResponse = new BYTE[ bResponse->Length ];
				cbOcspResponse = bResponse->Length;
				System::Runtime::InteropServices::Marshal::Copy( bResponse, 0, (IntPtr)bOcspResponse, bResponse->Length );

#endif
				DWORD cbResponse = 0;
				LPVOID pResponse = NULL;
				DWORD cbSignedResponse = 0;
				LPVOID pSignedResponse = NULL;
				DWORD cbBasicResponse = 0;
				LPVOID pBasicResponse = NULL;

				if( bOcspResponse != NULL && cbOcspResponse > 0 ) { 

					
					// decode the OCSP_RESPONSE_INFO object
					if( CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
											OCSP_RESPONSE,
											bOcspResponse,
											cbOcspResponse,
											CRYPT_DECODE_ALLOC_FLAG,
											NULL,
											&pResponse,
											&cbResponse
											))
					{
						POCSP_RESPONSE_INFO ocspResp = (OCSP_RESPONSE_INFO*)pResponse;
						if( ocspResp->dwStatus != 0 ) { 
							/// if the OCSP response status is not 0, then the OCSP response isn't usable. 
							/// A message is written to the system event log to show what the status code
							/// is.
							System::String^ ocspStatusMsg;
							switch( ocspResp->dwStatus ) { 
								case 1:
									ocspStatusMsg = gcnew System::String("(1) Malformed Request");
									break;
								case 2:
									ocspStatusMsg = gcnew System::String("(2) Internal Error");
									break;
								case 3:
									ocspStatusMsg = gcnew System::String("(3) Try Later");
									break;
								case 5:
									ocspStatusMsg = gcnew System::String("(5) Signature Required");
									break;
								case 6:
									ocspStatusMsg = gcnew System::String("(6) Unauthorized");
									break;
								default:
									ocspStatusMsg = gcnew System::String("(" + ocspResp->dwStatus + ") Unrecognized Status Code");
									break;
							}
							System::Diagnostics::EventLog^ log = gcnew System::Diagnostics::EventLog();
							log->Source = "SAFE Plugin";
							log->WriteEntry("OCSP response contained a non-success status code: " + ocspStatusMsg);
							log->Close();
						}
						else
						{
							// Get the OCSP_BASIC_SIGNED_RESPONSE_INFO object out of the response
							if( CryptDecodeObjectEx(
											X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
											OCSP_BASIC_SIGNED_RESPONSE,
											ocspResp->Value.pbData,
											ocspResp->Value.cbData,
											CRYPT_DECODE_ALLOC_FLAG,
											NULL,
											&pSignedResponse,
											&cbSignedResponse
											) ) 
							{
								POCSP_BASIC_SIGNED_RESPONSE_INFO pSignedInfo =
									(POCSP_BASIC_SIGNED_RESPONSE_INFO)pSignedResponse;

								// Get the OCSP_BASIC_RESPONSE_INFO out of the signed response
								if( CryptDecodeObjectEx(
												X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
												OCSP_BASIC_RESPONSE,
												pSignedInfo->ToBeSigned.pbData,
												pSignedInfo->ToBeSigned.cbData,
												CRYPT_DECODE_ALLOC_FLAG,
												NULL,
												&pBasicResponse,
												&cbBasicResponse) )
								{
									POCSP_BASIC_RESPONSE_INFO pBasicInfo = (POCSP_BASIC_RESPONSE_INFO)pBasicResponse;

									/// now that the decoded objects are available, the following must
									/// be done:
									/// 1.  Extract the OCSP signer certificate 
									/// 2.  Check the signature on the OCSP response
									/// 3.  Check that the OCSP certificate was issued by the same certificate
									/// that issued the cert in the chain being examined
									/// 4.  Validate the OCSP server certificate
									/// 5.  Find the certificate revocation status in the OCSP response
									/// 5.a. Check the thisUpdate and nextUpdate values to ensure the OCSP
									/// response is time-valid for the validation operation
									if( pSignedInfo->SignatureInfo.cCertEncoded > 0 )
									{
										// 1.  Extract the OCSP signer certificate 
										PCCERT_CONTEXT ocspCert = CertCreateCertificateContext( 
															X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 
															pSignedInfo->SignatureInfo.rgCertEncoded[0].pbData, 
															pSignedInfo->SignatureInfo.rgCertEncoded[0].cbData);

										// 2.  Check the signature on the OCSP response
										if( CryptVerifyCertificateSignatureEx(
													NULL,
													X509_ASN_ENCODING, 
													CRYPT_VERIFY_CERT_SIGN_SUBJECT_OCSP_BASIC_SIGNED_RESPONSE,
													pSignedInfo,
													CRYPT_VERIFY_CERT_SIGN_ISSUER_PUBKEY,
													(void*)(&ocspCert->pCertInfo->SubjectPublicKeyInfo),
													0,
													NULL) ) 
										{
											/// 3.  Validate that the OCSP signer's certificate was signed
											/// by the same certificate that issued the certificate in the signer's chain
											/// currently being validated.  This assures that the OCSP response is
											/// appropriate for a revocation check against the certificate.
											if( CryptVerifyCertificateSignature( 
												NULL, 
												PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, 
												ocspCert->pbCertEncoded, 
												ocspCert->cbCertEncoded, 
												&(chain->rgpElement[certIdx+1]->pCertContext->pCertInfo->SubjectPublicKeyInfo) ) )
											{
												/// 4.  now that we know it's the right OCSP response, validate the certificate
												/// chain for the OCSP responder cert
												if( PathResult::Good == ValidateOcspCertificate(ocspCert, validationTime) ) { 
													/// 5.  Find the revocation info for the target cert
													unsigned int reIdx = 0;
													bool found = false;
													while( reIdx < pBasicInfo->cResponseEntry && !found ) {
														if( 
															// check the serial number
															CertCompareIntegerBlob(
																&pBasicInfo->rgResponseEntry[reIdx].CertId.SerialNumber,
																&chain->rgpElement[certIdx]->pCertContext->pCertInfo->SerialNumber) )
														{
															// check that the issuer key hash matches the value in the CertID
															bool issuerKeyMatches = false;
															DWORD hashAlg = CertOIDToAlgId(pBasicInfo->rgResponseEntry[reIdx].CertId.HashAlgorithm.pszObjId);
															DWORD cbCertHash = 0;
															BYTE *pbCertHash = NULL;
															if( CryptHashCertificate(
																	NULL,
																	hashAlg,
																	0,
																	chain->rgpElement[certIdx+1]->pCertContext->pCertInfo->SubjectPublicKeyInfo.PublicKey.pbData,
																	chain->rgpElement[certIdx+1]->pCertContext->pCertInfo->SubjectPublicKeyInfo.PublicKey.cbData,
																	NULL,
																	&cbCertHash) )
															{
																pbCertHash = new BYTE[ cbCertHash ];
																if( CryptHashCertificate(
																	NULL,
																	hashAlg,
																	0,
																	chain->rgpElement[certIdx+1]->pCertContext->pCertInfo->SubjectPublicKeyInfo.PublicKey.pbData,
																	chain->rgpElement[certIdx+1]->pCertContext->pCertInfo->SubjectPublicKeyInfo.PublicKey.cbData,
																	pbCertHash,
																	&cbCertHash))
																{
																	
																	OCSP_CERT_ID ocspCertId = pBasicInfo->rgResponseEntry[reIdx].CertId;
																	CRYPT_HASH_BLOB ocspCertIdBlob = ocspCertId.IssuerKeyHash;
																	if( cbCertHash == ocspCertIdBlob.cbData )
																	{
																		issuerKeyMatches = 
																			(0==memcmp(ocspCertIdBlob.pbData,pbCertHash,cbCertHash*sizeof(BYTE)));
																	}

																}
																delete [] pbCertHash;
																pbCertHash = NULL;
															}	

															// check that the issuer DN hash matches
															bool issuerDnMatches = false;
															if(CryptHashCertificate(
																	NULL,
																	hashAlg,
																	0,
																	chain->rgpElement[certIdx]->pCertContext->pCertInfo->Issuer.pbData,
																	chain->rgpElement[certIdx]->pCertContext->pCertInfo->Issuer.cbData,
																	NULL,
																	&cbCertHash))
															{
																pbCertHash = new BYTE[ cbCertHash ];
																if(CryptHashCertificate(
																	NULL,
																	hashAlg,
																	0,
																	chain->rgpElement[certIdx]->pCertContext->pCertInfo->Issuer.pbData,
																	chain->rgpElement[certIdx]->pCertContext->pCertInfo->Issuer.cbData,
																	pbCertHash,
																	&cbCertHash))
																{
																	OCSP_CERT_ID ocspCertId = pBasicInfo->rgResponseEntry[reIdx].CertId;
																	CRYPT_HASH_BLOB ocspCertIdBlob = ocspCertId.IssuerNameHash;
																	if( cbCertHash == ocspCertIdBlob.cbData )
																	{
																		issuerDnMatches = 
																			(0==memcmp(ocspCertIdBlob.pbData,pbCertHash,cbCertHash*sizeof(BYTE)));
																	}
																}
																delete [] pbCertHash;
															}

															
															found = issuerDnMatches && issuerKeyMatches;
															bool timeValid = false;


															if( found ) { 
																// 5.a the cert ID matched up.  Now, check the time validity
																FILETIME tu = pBasicInfo->rgResponseEntry[reIdx].ThisUpdate;
																FILETIME nu = pBasicInfo->rgResponseEntry[reIdx].NextUpdate;

																System::DateTime dtThisUpdate = DateTime::FromFileTimeUtc(
																			(((__int64)tu.dwHighDateTime) << 32) | (tu.dwLowDateTime & 0xFFFFFFFF));
																System::DateTime dtNextUpdate = DateTime::FromFileTimeUtc(
																			(((__int64)nu.dwHighDateTime) << 32) | (nu.dwLowDateTime & 0xFFFFFFFF));
																System::DateTime dtValTime = DateTime::FromFileTimeUtc( 
																			(((__int64)validationTime.dwHighDateTime) << 32) | (validationTime.dwLowDateTime & 0xFFFFFFFF));

																// dates are checked differently if there is no nextUpdate available in the OCSP
																// response
																// REVIEW:  are these the right values to compare against for the case of the
																// nextUpdate value not being present in the OCSP response?
																if( dtNextUpdate == DateTime::MinValue || dtNextUpdate == DateTime::MaxValue ) { 
																	System::TimeSpan^ diff = dtValTime - dtThisUpdate;
																	if( dtValTime < dtThisUpdate.Subtract( System::TimeSpan::FromMilliseconds(clockSkew) ) ) { 
																		// if the timestamp is less than the thisUpdate value in the OCSP response,
																		// then the OCSP response is not considered valid
																		// a 5 minute window is allowed for clock skew between the OCSP and timestamp
																		// servers
																		timeValid = false;
																	} else { 
																		timeValid = true;
																	}
																} else { 
																	if( dtValTime < dtThisUpdate.Subtract( System::TimeSpan::FromMilliseconds(clockSkew) ) ||
																		dtValTime > dtNextUpdate.Add( System::TimeSpan::FromMinutes(5)) ) {
																		// if the timestamp is less than the thisUpdate or greater than the 
																		// nextUpdate value, then the OCSP response is not considered valid
																		// a 5 minute window is allowed for clock skew between the OCSP and timestamp
																		// servers
																		timeValid = false;
																	} else { 
																		timeValid = true;
																	}
																}
															}
															found &= timeValid;
															if( found ) { 
																ocspFoundForCert = true;
																// get the certificate validity from the OCSP response
																DWORD status = pBasicInfo->rgResponseEntry[reIdx].dwCertStatus;
																switch( status ) { 
																	case 0: // good
																		// do nothing, cert is valid
																		break;
																	case 1:
																		// set the return value to REVOKED
																		rval = PathResult::CertRevoked;
																		break;
																	case 2:
																		// set ocspFoundForCert to false, since the revocation
																		// status is unknown
																		ocspFoundForCert = false;
																		found = false;
																		//System::Windows::Forms::MessageBox::Show("OCSP Response has code 2");
																		break;
																}
															}
														}
														reIdx++;
													}
												} else {
													// Handle invalid OCSP certificate by skipping this OCSP response
												}
											} else {
												// handle invalid OCSP signer by skipping this OCSP response
											}
										} else {
											// handle invalid OCSP signature by skipping this OCSP response
										}
										if( ocspCert != NULL ) { 
											CertFreeCertificateContext(ocspCert);
										}
									}
								}
							}
						}
					}
					if( pBasicResponse != NULL ) {
						LocalFree(pBasicResponse);
					}
					if( pSignedResponse != NULL ) { 
						LocalFree(pSignedResponse);
					}
					if( pResponse != NULL ) { 
						LocalFree(pResponse);
					}
					delete [] bOcspResponse;
					bOcspResponse = NULL;
				}
#ifdef USING_OCSP_CRLS_ONLY
				CertFreeCRLContext(pCrl);
#endif
			}
			if( !ocspFoundForCert ) { 
				rval = PathResult::RevocationUnavailable;
			}
		}
		return rval;
	}
		

	/// To validate the OCSP certificate, examine the OCSP cert for the NOCHECK flag.  If
	/// the NOCHECK flag is present, then build a path to a trust root with no revocation
	/// checking.  If not, then build a path to the trust root with revocation checking.
	PathResult CertificateValidation::ValidateOcspCertificate( PCCERT_CONTEXT pOcspCert, FILETIME timestamp ) {
		// if the nocheck flag is present, don't check revocation for the OCSP cert	
		bool checkRev = (NULL == CertFindExtension( 
			szOID_PKIX_OCSP_NOCHECK, 
			pOcspCert->pCertInfo->cExtension, 
			pOcspCert->pCertInfo->rgExtension ));

		PathResult rval = PathResult::Good;
		PCCERT_CHAIN_CONTEXT pChainContext = NULL;
		CERT_CHAIN_PARA          chainPara;
		CERT_USAGE_MATCH		 certUsage;
		
		memset( &chainPara, 0, sizeof(CERT_CHAIN_PARA) );
		memset( &certUsage, 0, sizeof(CERT_USAGE_MATCH) );

		// make sure the OCSP cert has the OCSP signing enhanced key usage
		certUsage.dwType = USAGE_MATCH_TYPE_AND;
		certUsage.Usage.cUsageIdentifier = 1;
		LPSTR arr[1];
		int length = strlen( szOID_PKIX_KP_OCSP_SIGNING ) + 1;
		arr[0] = new char[ length ];
		memset( arr[0], 0, length * sizeof(char) );
		strcpy_s(arr[0], length, szOID_PKIX_KP_OCSP_SIGNING );
		certUsage.Usage.rgpszUsageIdentifier = arr;

		
		//-------------------------------------------------------
		// Set the chain building parameters
		chainPara.cbSize = sizeof(CERT_CHAIN_PARA);
		chainPara.RequestedUsage=certUsage;
		chainPara.dwRevocationFreshnessTime = 0;
		chainPara.dwUrlRetrievalTimeout = 600;
		chainPara.fCheckRevocationFreshnessTime = FALSE;
		chainPara.RequestedUsage = certUsage;
		//-------------------------------------------------------
		if( CertGetCertificateChain( 
					NULL, 
					pOcspCert, 
					&timestamp, 
					NULL, 
					&chainPara, 
					CERT_CHAIN_TIMESTAMP_TIME, 
					NULL, 
					&pChainContext) && pChainContext->cChain > 0 ) { 

			DWORD status = pChainContext->TrustStatus.dwErrorStatus;
			// remove non-standard MS errors
			status &= REMOVE_NONSTANDARD_ERRORS_MASK;
			status &= REMOVE_TIMEVALID_ERROR_MASK;
			
			if( !checkRev ) { 
				// remove revocation errors if not checking for revocation
				// (i.e., if the OCSP-NOCHECK flag is in the cert)
				status &= REMOVE_REVOCATION_STATUS_MASK;
			}

			if(pChainContext->cChain > 0 && 
				!CheckChainTimeValidity(pChainContext->rgpChain[0], timestamp)) {
				rval = PathResult::ChainNotTimeValid;
			}
			

			if( status == CERT_TRUST_NO_ERROR ) { 
				rval = PathResult::Good;
			} else if( status & CERT_TRUST_REVOCATION_STATUS_UNKNOWN ) { 
				rval = PathResult::OcspRevocationUnavailable;
			} else if( status & CERT_TRUST_IS_REVOKED ) { 
				rval = PathResult::OcspCertRevoked;
			} else { 
				rval = PathResult::OcspCertInvalid;
			}
			CertFreeCertificateChain( pChainContext );
			pChainContext = NULL;
		} else { 
			rval = PathResult::OcspCertInvalid;
		}

		delete [] arr[0];
		return rval;
	}



	/// Validates a signer's certificate and retrieves the OCSP responses from
	/// the constructed certificate chain
	System::Collections::Generic::List<System::String^>^
		CertificateValidation::GetOcspResponses(X509Certificate2^ cert,
		System::Collections::Generic::List<System::String^>^ safePolicyOids,
		long clockSkew )
	{
		System::Collections::Generic::List<System::String^>^ rval = 
			gcnew System::Collections::Generic::List<System::String^>();
		PCCERT_CHAIN_CONTEXT     pChainContext;
		PCCERT_CONTEXT           pCertContext = NULL;
		CERT_CHAIN_PARA          chainPara;
		CERT_USAGE_MATCH		 certUsage;
		CERT_USAGE_MATCH		 safePolicy;
		DWORD                    dwFlags=0;
		
		memset( &certUsage, 0, sizeof(CERT_USAGE_MATCH) );
		memset( &chainPara, 0, sizeof(CERT_CHAIN_PARA) );
		memset( &safePolicy, 0, sizeof(CERT_USAGE_MATCH) );

		if( safePolicyOids != nullptr && safePolicyOids->Count > 0 ) { 
			safePolicy.dwType = USAGE_MATCH_TYPE_OR;
			safePolicy.Usage.cUsageIdentifier = safePolicyOids->Count;
			safePolicy.Usage.rgpszUsageIdentifier = new LPSTR[ safePolicy.Usage.cUsageIdentifier ];
			for( int policyIdx = 0; policyIdx < safePolicyOids->Count; policyIdx++ ) { 
				System::IntPtr ptr = System::Runtime::InteropServices::Marshal::StringToHGlobalAnsi(safePolicyOids[policyIdx]);
				safePolicy.Usage.rgpszUsageIdentifier[policyIdx] = static_cast<char*>(ptr.ToPointer());
			}
		}
		
		//-------------------------------------------------------
		// Get the cert context
		pCertContext = (PCCERT_CONTEXT)(void *)cert->Handle;

		//-------------------------------------------------------
		// Set the chain building parameters
		// (Note:  aside from .RequestedUsage and .cbSize, the
		// rest of the members of the CERT_CHAIN_PARA struct are
		// not accessible by default.  The #define macro for 
		// CERT_CHAIN_PARA_HAS_EXTRA_FIELDS in stdafx.h makes the
		// rest of these fields visible).  This allows 
		// specifying the policy information when building the 
		// chain rather than looking for it after the chain has 
		// been built.
		chainPara.cbSize = sizeof(CERT_CHAIN_PARA);
		chainPara.RequestedUsage=certUsage;
		chainPara.dwRevocationFreshnessTime = 0;
		chainPara.dwUrlRetrievalTimeout = 600;
		chainPara.fCheckRevocationFreshnessTime = FALSE;
		if( safePolicy.Usage.cUsageIdentifier > 0 ) { 
			chainPara.RequestedIssuancePolicy = safePolicy;
		}
		chainPara.RequestedUsage = certUsage;
		//-------------------------------------------------------


		//-------------------------------------------------------------------
		// Build a chain using CertGetCertificateChain
		// and the certificate retrieved.
		BOOL chainSuccess = CertGetCertificateChain(
					NULL,                  // use the default chain engine
					pCertContext,          // pointer to the end certificate
					NULL,                  // use the current system time
					NULL,                  // search no additional stores
					&chainPara,            // contains policy processing information
					CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT, //dwFlags,
					NULL,                  // currently reserved
					&pChainContext);       // return a pointer to the chain created
		
		// release the allocated policy information
		if( safePolicy.Usage.cUsageIdentifier > 0 ) { 
			for( unsigned int policyIdx = 0; policyIdx < safePolicy.Usage.cUsageIdentifier; policyIdx++ ) { 
				System::Runtime::InteropServices::Marshal::FreeHGlobal( (IntPtr)safePolicy.Usage.rgpszUsageIdentifier[policyIdx] );
			}
			safePolicy.Usage.cUsageIdentifier = 0;
		}
		

		if( chainSuccess ) {
			System::Diagnostics::Debug::WriteLine("The chain has been created. \n");
		}
		else
		{
			throw gcnew Safe::ChainBuildException("Chain building failed");
		}
		

		// Get OCSP Responses from within the chain
		PCERT_CHAIN_ELEMENT* pcertChainElement;
		PCERT_SIMPLE_CHAIN psChain;
		PCERT_REVOCATION_INFO pRevocInfo;
		PCERT_REVOCATION_CRL_INFO pRevocCRLInfo;
		PCRL_INFO pCRLInfo;

		
		if (pChainContext->cChain > 0 && pChainContext->TrustStatus.dwErrorStatus == CERT_TRUST_NO_ERROR)
		{
			psChain = pChainContext->rgpChain[0];
			pcertChainElement = psChain->rgpElement;
			bool requiresRevalidation = false;

			// the "certCount < psChain->cElement - 1" does not include the root certificate
			for (unsigned int certCount = 0; certCount < psChain->cElement - 1; certCount++)
			{
				PCERT_CHAIN_ELEMENT aCert = pcertChainElement[certCount];
				
				// Look in the pRevocationInfo field to get either the CRL or OCSP used in the 
				// chain validation. 
				// This info is represented by a CERT_REVOCATION_INFO structure
				pRevocInfo = aCert->pRevocationInfo;
				// Look in the pCrlInfo field (type CERT_REVOCATION_CRL_INFO) for more 
				// details on the CRL or OCSP
				pRevocCRLInfo =  pRevocInfo->pCrlInfo;
				// Go into the pBaseCRLContext field (type CRL_CONTEXT), then the pCrlInfo 
				// field (type CRL_INFO) to get to the details of the CRL 
				pCRLInfo = pRevocCRLInfo->pBaseCrlContext->pCrlInfo;
				// Look for an extension in the CRL with the OID 
				// szOID_PKIX_OCSP_BASIC_SIGNED_RESPONSE (1.3.6.1.5.5.7.48.1.1). 
				// You can call the CertFindExtension API with the OID, the rgExtensions 
				// and cExtensions fields
				PCERT_EXTENSION pExt = NULL;
				if( NULL == (pExt=CertFindExtension(
								szOID_PKIX_OCSP_BASIC_SIGNED_RESPONSE, 
								pCRLInfo->cExtension, 
								pCRLInfo->rgExtension)) ) 
				{ 
#ifndef USING_OCSP_CRLS_ONLY
					requiresRevalidation = true;
					try {
						System::Diagnostics::EventLog^ log = gcnew System::Diagnostics::EventLog();
						log->Source = "SAFE Plugin";
						log->WriteEntry("OCSP response not found for certificate " + certCount + " in the chain.  Retrieving manually");
						log->Close();
					} catch(Exception^ exc) { 
						throw gcnew Safe::OcspException("OCSP Responses could not be obtained for all certificates in the chain");
					}

					PCCERT_CHAIN_CONTEXT pOcspChainContext = NULL;
					// need to build a new certificate chain if the current index is not the end user
					if( certCount != 0 ) {
						CERT_CHAIN_PARA innerParams;
						memset(&innerParams, 0, sizeof(CERT_CHAIN_PARA) );
						innerParams.cbSize = sizeof(CERT_CHAIN_PARA);
						innerParams.dwRevocationFreshnessTime = 0;
						innerParams.dwUrlRetrievalTimeout = 600;
						CertGetCertificateChain(
								NULL,
								psChain->rgpElement[certCount]->pCertContext,
								NULL,
								NULL,
								&chainPara,
								CERT_CHAIN_REVOCATION_CHECK_END_CERT,
								NULL,
								&pOcspChainContext);
					} else {
						pOcspChainContext = CertDuplicateCertificateChain(pChainContext);
					}
					

					// if the revocation wasn't checked with OCSP, we need to fetch the OCSP 
					// response ourselves.  If this is for a cert other than the end user cert,
					// then we need to build a new chain from the current cert in the chain back
					// to the root in order to get the current cert's OCSP data.
					HCERT_SERVER_OCSP_RESPONSE hOcspResp = NULL;
					if( pOcspChainContext != NULL ) {
						hOcspResp = CertOpenServerOcspResponse(
																pOcspChainContext,
																0,
																NULL );

						CertFreeCertificateChain(pOcspChainContext);
					}

					if( hOcspResp != NULL ) { 
						PCCERT_SERVER_OCSP_RESPONSE_CONTEXT pOcspContext = CertGetServerOcspResponseContext( hOcspResp, 0, NULL );
						if( pOcspContext != NULL && pOcspContext->pbEncodedOcspResponse != NULL ) { 
							DWORD cEnc = pOcspContext->cbEncodedOcspResponse;
							BYTE* pEnc = pOcspContext->pbEncodedOcspResponse;
							array<unsigned char>^ arr = gcnew array<unsigned char>( cEnc );
							System::Runtime::InteropServices::Marshal::Copy(
								(IntPtr)pEnc, arr, 0, arr->Length);
							String^ b64ocsp = System::Convert::ToBase64String(arr);
							rval->Add(b64ocsp);
						}
						CertCloseServerOcspResponse( hOcspResp, 0 );


						System::Diagnostics::Trace::WriteLine("OCSP Response retrieved manually");
					} 
					else 
#endif
					{ 
						throw gcnew Safe::OcspException("A certificate in the chain did not have an appropriate OCSP response");
					}

				} else { 
					// convert any native ptr to System::IntPtr by doing C-Style cast 
					array<unsigned char>^ arr = gcnew array<unsigned char>(
#ifdef USING_OCSP_CRLS_ONLY
						pRevocInfo->pCrlInfo->pBaseCrlContext->cbCrlEncoded);
#else
						pExt->Value.cbData); 
#endif
					System::Runtime::InteropServices::Marshal::Copy(
#ifdef USING_OCSP_CRLS_ONLY
						(IntPtr)pRevocInfo->pCrlInfo->pBaseCrlContext->pbCrlEncoded, 
#else
						(IntPtr)pExt->Value.pbData,
#endif
						arr, 0, arr->Length);
					String^ b64ocsp = System::Convert::ToBase64String(arr);
					rval->Add(b64ocsp);
					System::Diagnostics::Trace::WriteLine("OCSP Response found");
				}

				
			}
			if( requiresRevalidation ) { 
				Safe::PathResult revalResult = ValidateCertificate( cert, rval, safePolicyOids, System::DateTime::Now, clockSkew ); 
				if( Safe::PathResult::Good != revalResult )
				{
					if( Safe::PathResult::RevocationUnavailable == revalResult ) { 
						throw gcnew Safe::OcspException("OCSP Responses could not be obtained for all certificates in the chain");
					} else { 
						throw gcnew Safe::ChainBuildException("A trusted certificate chain could not be created (" + 
							((int)revalResult).ToString() + ")");
					}
				}
			}

		} else if( pChainContext->cChain > 0 ) { 			

			if( (pChainContext->TrustStatus.dwErrorStatus & CERT_TRUST_IS_NOT_VALID_FOR_USAGE) != 0 ) { 
				throw gcnew Safe::ChainBuildException("The selected certificate does not express a valid SAFE policy");
			} else { 
				throw gcnew Safe::ChainBuildException("A trusted certificate chain could not be created:\n" +
					PathBuildErrorToString(pChainContext->TrustStatus.dwErrorStatus) );
			}

		} else { 
			throw gcnew Safe::ChainBuildException("The chain could not be created");
		}

		return rval;
	}	




	String^ CertificateValidation::PathBuildErrorToString(DWORD err) {
		System::IO::StringWriter^ buf = gcnew System::IO::StringWriter();
		if( err & CERT_TRUST_NO_ERROR ) { 
			buf->WriteLine("No error found for this certificate or chain.");
		}
		if( err &  CERT_TRUST_IS_NOT_TIME_VALID ) {
			buf->WriteLine("One of the certificates in the certificate chain is not time valid.");
		}
		if( err &  CERT_TRUST_IS_NOT_TIME_NESTED ) {
			buf->WriteLine("Certificates in the chain are not properly time nested.");
		}
		if( err &  CERT_TRUST_IS_REVOKED ) {
			buf->WriteLine("One of the certificates in the certificate chain is revoked");
		}
		if( err &  CERT_TRUST_IS_NOT_SIGNATURE_VALID ) {
			buf->WriteLine("The certificate or one of the certificates in the certificate chain does not have a valid signature.");
		}
		if( err &  CERT_TRUST_IS_NOT_VALID_FOR_USAGE ) {
			buf->WriteLine("The certificate or certificate chain is not valid for its proposed usage.");
		}
		if( err &  CERT_TRUST_IS_UNTRUSTED_ROOT ) {
			buf->WriteLine("The certificate or certificate chain is based on an untrusted root.");
		}
		if( err &  CERT_TRUST_REVOCATION_STATUS_UNKNOWN ) {
			buf->WriteLine("The revocation status of the certificate or one of the certificates in the certificate chain is unknown.");
		}
		if( err &  CERT_TRUST_IS_CYCLIC ) {
			buf->WriteLine("One of the certificates in the chain was issued by a certification authority that the original certificate had certified.");
		}
		if( err &  CERT_TRUST_INVALID_EXTENSION ) {
			buf->WriteLine("One of the certificates has an extension that is not valid.");
		}
		if( err &  CERT_TRUST_INVALID_POLICY_CONSTRAINTS ) {
			buf->WriteLine("The certificate or one of the certificates in the certificate chain has a policy constraints extension, and one of the issued certificates has a disallowed policy mapping extension or does not have a required issuance policies extension.");
		}
		if( err &  CERT_TRUST_INVALID_BASIC_CONSTRAINTS ) {
			buf->WriteLine("The certificate or one of the certificates in the certificate chain has a basic constraints extension, and either the certificate cannot be used to issue other certificates, or the chain path length has been exceeded.");
		}
		if( err &  CERT_TRUST_INVALID_NAME_CONSTRAINTS ) {
			buf->WriteLine("The certificate or one of the certificates in the certificate chain has a name constraints extension that is not valid.");
		}
		if( err &  CERT_TRUST_HAS_NOT_SUPPORTED_NAME_CONSTRAINT ) {
			buf->WriteLine("The certificate or one of the certificates in the certificate chain has a name constraints extension that contains unsupported fields. The minimum and maximum fields are not supported. Thus minimum must always be zero and maximum must always be absent. Only UPN is supported for an Other Name.");
		}
		if( err &  CERT_TRUST_HAS_NOT_DEFINED_NAME_CONSTRAINT ) {
			buf->WriteLine("The certificate or one of the certificates in the certificate chain has a name constraints extension and a name constraint is missing for one of the name choices in the end certificate.");
		}
		if( err &  CERT_TRUST_HAS_NOT_PERMITTED_NAME_CONSTRAINT ) {
			buf->WriteLine("The certificate or one of the certificates in the certificate chain has a name constraints extension, and there is not a permitted name constraint for one of the name choices in the end certificate.");
		}
		if( err &  CERT_TRUST_HAS_EXCLUDED_NAME_CONSTRAINT ) {
			buf->WriteLine("The certificate or one of the certificates in the certificate chain has a name constraints extension, and one of the name choices in the end certificate is explicitly excluded.");
		}
		if( err &  CERT_TRUST_IS_OFFLINE_REVOCATION ) {
			buf->WriteLine("The revocation status of the certificate or one of the certificates in the certificate chain is either offline or stale.");
		}
		if( err &  CERT_TRUST_NO_ISSUANCE_CHAIN_POLICY ) {
			buf->WriteLine("The end certificate does not have any resultant issuance policies, and one of the issuing certification authority certificates has a policy constraints extension requiring it.");
		}
		if( err &  CERT_TRUST_IS_EXPLICIT_DISTRUST ) {
			buf->WriteLine("The certificate is explicitly distrusted.");
		}
		if( err &  CERT_TRUST_HAS_NOT_SUPPORTED_CRITICAL_EXT ) {
			buf->WriteLine("The certificate has an unsupported critical extension");
		}
		if( err &  CERT_TRUST_IS_PARTIAL_CHAIN ) {
			buf->WriteLine("The certificate chain is not complete.");
		}
		if( err &  CERT_TRUST_CTL_IS_NOT_TIME_VALID ) {
			buf->WriteLine("A certificate trust list (CTL) used to create this chain was not time valid.");
		}
		if( err &  CERT_TRUST_CTL_IS_NOT_SIGNATURE_VALID ) {
			buf->WriteLine("A CTL used to create this chain did not have a valid signature.");
		}
		if( err &  CERT_TRUST_CTL_IS_NOT_VALID_FOR_USAGE ) {
			buf->WriteLine("A CTL used to create this chain is not valid for this usage.");
		}
		if( err == 0 ) { 
			buf->WriteLine("No Error");
		}
		return  buf->ToString();
	}

}